فارسی

بررسی نمادهای جاوا اسکریپت: هدف، ایجاد و کاربرد آنها برای کلیدهای منحصر به فرد، ذخیره فراداده‌ها و جلوگیری از تداخل نام‌ها. همراه با مثال‌های عملی.

نمادهای جاوا اسکریپت: کلیدهای منحصر به فرد و فراداده‌ها

نمادهای جاوا اسکریپت که در ECMAScript 2015 (ES6) معرفی شدند، مکانیزمی برای ایجاد کلیدهای ویژگی (property keys) منحصر به فرد و غیرقابل تغییر فراهم می‌کنند. برخلاف رشته‌ها یا اعداد، نمادها تضمین شده‌اند که در کل برنامه جاوا اسکریپت شما منحصر به فرد باشند. آنها راهی برای جلوگیری از تداخل نام‌ها، پیوست کردن فراداده‌ها به اشیاء بدون تداخل با ویژگی‌های موجود و سفارشی‌سازی رفتار اشیاء ارائه می‌دهند. این مقاله یک نمای کلی جامع از نمادهای جاوا اسکریپت را ارائه می‌دهد که شامل ایجاد، کاربردها و بهترین شیوه‌های استفاده از آنها می‌شود.

نمادهای جاوا اسکریپت چه هستند؟

نماد (Symbol) یک نوع داده اولیه (primitive) در جاوا اسکریپت است، مشابه اعداد، رشته‌ها، بولین‌ها، null و undefined. با این حال، برخلاف سایر انواع داده اولیه، نمادها منحصر به فرد هستند. هر بار که یک نماد ایجاد می‌کنید، یک مقدار کاملاً جدید و منحصر به فرد دریافت می‌کنید. این منحصر به فرد بودن، نمادها را برای موارد زیر ایده‌آل می‌کند:

ایجاد نمادها

شما با استفاده از سازنده Symbol() یک نماد ایجاد می‌کنید. مهم است که توجه داشته باشید نمی‌توانید از new Symbol() استفاده کنید؛ نمادها شیء نیستند، بلکه مقادیر اولیه هستند.

ایجاد نماد پایه

ساده‌ترین راه برای ایجاد یک نماد این است:

const mySymbol = Symbol();
console.log(typeof mySymbol); // خروجی: symbol

هر فراخوانی Symbol() یک مقدار جدید و منحصر به فرد تولید می‌کند:

const symbol1 = Symbol();
const symbol2 = Symbol();
console.log(symbol1 === symbol2); // خروجی: false

توضیحات نماد

شما می‌توانید هنگام ایجاد یک نماد، یک توضیح رشته‌ای اختیاری ارائه دهید. این توضیح برای اشکال‌زدایی (debugging) و ثبت وقایع (logging) مفید است، اما بر منحصر به فرد بودن نماد تأثیری ندارد.

const mySymbol = Symbol("myDescription");
console.log(mySymbol.toString()); // خروجی: Symbol(myDescription)

این توضیح صرفاً برای اهداف اطلاعاتی است؛ دو نماد با توضیحات یکسان همچنان منحصر به فرد هستند:

const symbolA = Symbol("same description");
const symbolB = Symbol("same description");
console.log(symbolA === symbolB); // خروجی: false

استفاده از نمادها به عنوان کلید ویژگی

نمادها به ویژه به عنوان کلید ویژگی مفید هستند زیرا منحصر به فرد بودن را تضمین می‌کنند و از تداخل نام‌ها هنگام افزودن ویژگی به اشیاء جلوگیری می‌کنند.

افزودن ویژگی‌های نماد

شما می‌توانید از نمادها به عنوان کلید ویژگی دقیقاً مانند رشته‌ها یا اعداد استفاده کنید:

const mySymbol = Symbol("myKey");
const myObject = {};

myObject[mySymbol] = "Hello, Symbol!";

console.log(myObject[mySymbol]); // خروجی: Hello, Symbol!

جلوگیری از تداخل نام‌ها

تصور کنید در حال کار با یک کتابخانه شخص ثالث هستید که ویژگی‌هایی به اشیاء اضافه می‌کند. ممکن است بخواهید ویژگی‌های خود را بدون خطر بازنویسی ویژگی‌های موجود اضافه کنید. نمادها راهی امن برای انجام این کار فراهم می‌کنند:

// کتابخانه شخص ثالث (شبیه‌سازی شده)
const libraryObject = {
  name: "Library Object",
  version: "1.0"
};

// کد شما
const mySecretKey = Symbol("mySecret");
libraryObject[mySecretKey] = "Top Secret Information";

console.log(libraryObject.name); // خروجی: Library Object
console.log(libraryObject[mySecretKey]); // خروجی: Top Secret Information

در این مثال، mySecretKey تضمین می‌کند که ویژگی شما با هیچ یک از ویژگی‌های موجود در libraryObject تداخل نداشته باشد.

شمارش ویژگی‌های نماد

یکی از ویژگی‌های مهم ویژگی‌های نماد این است که آنها از روش‌های شمارش استاندارد مانند حلقه‌های for...in و Object.keys() پنهان هستند. این به محافظت از یکپارچگی اشیاء کمک می‌کند و از دسترسی یا تغییر تصادفی ویژگی‌های نماد جلوگیری می‌کند.

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

console.log(Object.keys(myObject)); // خروجی: ["name"]

for (let key in myObject) {
  console.log(key); // خروجی: name
}

برای دسترسی به ویژگی‌های نماد، باید از Object.getOwnPropertySymbols() استفاده کنید که آرایه‌ای از تمام ویژگی‌های نماد روی یک شیء را برمی‌گرداند:

const mySymbol = Symbol("myKey");
const myObject = {
  name: "My Object",
  [mySymbol]: "Symbol Value"
};

const symbolKeys = Object.getOwnPropertySymbols(myObject);
console.log(symbolKeys); // خروجی: [Symbol(myKey)]
console.log(myObject[symbolKeys[0]]); // خروجی: Symbol Value

نمادهای شناخته شده (Well-Known Symbols)

جاوا اسکریپت مجموعه‌ای از نمادهای داخلی را ارائه می‌دهد که به عنوان نمادهای شناخته شده شناخته می‌شوند و رفتارهای یا قابلیت‌های خاصی را نشان می‌دهند. این نمادها ویژگی‌های سازنده Symbol هستند (مثلاً Symbol.iterator، Symbol.toStringTag). آنها به شما امکان می‌دهند رفتار اشیاء را در زمینه‌های مختلف سفارشی کنید.

Symbol.iterator

Symbol.iterator نمادی است که تکرارکننده (iterator) پیش‌فرض را برای یک شیء تعریف می‌کند. وقتی یک شیء دارای متدی با کلید Symbol.iterator باشد، آن شیء تکرارپذیر (iterable) می‌شود، به این معنی که می‌توانید از آن با حلقه‌های for...of و عملگر spread (...) استفاده کنید.

مثال: ایجاد یک شیء تکرارپذیر سفارشی

const myCollection = {
  items: [1, 2, 3, 4, 5],
  [Symbol.iterator]: function* () {
    for (let item of this.items) {
      yield item;
    }
  }
};

for (let item of myCollection) {
  console.log(item); // خروجی: 1, 2, 3, 4, 5
}

console.log([...myCollection]); // خروجی: [1, 2, 3, 4, 5]

در این مثال، myCollection یک شیء است که پروتکل تکرارکننده را با استفاده از Symbol.iterator پیاده‌سازی می‌کند. تابع مولد (generator function) هر آیتم را در آرایه items تولید (yield) می‌کند و myCollection را تکرارپذیر می‌سازد.

Symbol.toStringTag

Symbol.toStringTag نمادی است که به شما امکان می‌دهد نمایش رشته‌ای یک شیء را هنگام فراخوانی Object.prototype.toString() سفارشی کنید.

مثال: سفارشی‌سازی نمایش ()toString

class MyClass {
  get [Symbol.toStringTag]() {
    return 'MyClassInstance';
  }
}

const instance = new MyClass();
console.log(Object.prototype.toString.call(instance)); // خروجی: [object MyClassInstance]

بدون Symbol.toStringTag، خروجی [object Object] خواهد بود. این نماد راهی برای ارائه نمایش رشته‌ای توصیفی‌تر از اشیاء شما فراهم می‌کند.

Symbol.hasInstance

Symbol.hasInstance نمادی است که به شما امکان می‌دهد رفتار عملگر instanceof را سفارشی کنید. به طور معمول، instanceof بررسی می‌کند که آیا زنجیره прототип یک شیء حاوی ویژگی prototype یک سازنده است یا خیر. Symbol.hasInstance به شما امکان می‌دهد این رفتار را نادیده بگیرید.

مثال: سفارشی‌سازی بررسی instanceof

class MyClass {
  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance);
  }
}

console.log([] instanceof MyClass); // خروجی: true
console.log({} instanceof MyClass); // خروجی: false

در این مثال، متد Symbol.hasInstance بررسی می‌کند که آیا نمونه (instance) یک آرایه است یا خیر. این به طور موثر باعث می‌شود MyClass به عنوان یک بررسی‌کننده برای آرایه‌ها عمل کند، صرف نظر از زنجیره прототип واقعی.

سایر نمادهای شناخته شده

جاوا اسکریپت چندین نماد شناخته شده دیگر را نیز تعریف می‌کند، از جمله:

رجیستری سراسری نمادها

گاهی اوقات، شما نیاز دارید که نمادها را در بخش‌های مختلف برنامه خود یا حتی بین برنامه‌های مختلف به اشتراک بگذارید. رجیستری سراسری نمادها مکانیزمی برای ثبت و بازیابی نمادها بر اساس یک کلید فراهم می‌کند.

Symbol.for(key)

متد Symbol.for(key) بررسی می‌کند که آیا نمادی با کلید داده شده در رجیستری سراسری وجود دارد یا خیر. اگر وجود داشته باشد، آن نماد را برمی‌گرداند. اگر وجود نداشته باشد، یک نماد جدید با آن کلید ایجاد کرده و آن را در رجیستری ثبت می‌کند.

const globalSymbol1 = Symbol.for("myGlobalSymbol");
const globalSymbol2 = Symbol.for("myGlobalSymbol");

console.log(globalSymbol1 === globalSymbol2); // خروجی: true
console.log(Symbol.keyFor(globalSymbol1)); // خروجی: myGlobalSymbol

Symbol.keyFor(symbol)

متد Symbol.keyFor(symbol) کلید مرتبط با یک نماد را در رجیستری سراسری برمی‌گرداند. اگر نماد در رجیستری نباشد، undefined برمی‌گرداند.

const mySymbol = Symbol("localSymbol");
console.log(Symbol.keyFor(mySymbol)); // خروجی: undefined

const globalSymbol = Symbol.for("myGlobalSymbol");
console.log(Symbol.keyFor(globalSymbol)); // خروجی: myGlobalSymbol

مهم: نمادهایی که با Symbol() ایجاد می‌شوند، به طور خودکار در رجیستری سراسری ثبت *نمی‌شوند*. فقط نمادهایی که با Symbol.for() ایجاد (یا بازیابی) می‌شوند، بخشی از رجیستری هستند.

مثال‌های عملی و موارد استفاده

در اینجا چند مثال عملی وجود دارد که نشان می‌دهد چگونه می‌توان از نمادها در سناریوهای دنیای واقعی استفاده کرد:

۱. ایجاد سیستم‌های پلاگین

از نمادها می‌توان برای ایجاد سیستم‌های پلاگین استفاده کرد که در آن ماژول‌های مختلف می‌توانند عملکرد یک شیء اصلی را بدون تداخل با ویژگی‌های یکدیگر گسترش دهند.

// شیء اصلی
const coreObject = {
  name: "Core Object",
  version: "1.0"
};

// پلاگین ۱
const plugin1Key = Symbol("plugin1");
coreObject[plugin1Key] = {
  description: "پلاگین ۱ قابلیت‌های اضافی اضافه می‌کند",
  activate: function() {
    console.log("پلاگین ۱ فعال شد");
  }
};

// پلاگین ۲
const plugin2Key = Symbol("plugin2");
coreObject[plugin2Key] = {
  author: "توسعه‌دهنده دیگر",
  init: function() {
    console.log("پلاگین ۲ مقداردهی اولیه شد");
  }
};

// دسترسی به پلاگین‌ها
console.log(coreObject[plugin1Key].description); // خروجی: پلاگین ۱ قابلیت‌های اضافی اضافه می‌کند
coreObject[plugin2Key].init(); // خروجی: پلاگین ۲ مقداردهی اولیه شد

در این مثال، هر پلاگین از یک کلید نماد منحصر به فرد استفاده می‌کند که از تداخل نام‌های احتمالی جلوگیری کرده و تضمین می‌کند که پلاگین‌ها می‌توانند به صورت مسالمت‌آمیز در کنار هم وجود داشته باشند.

۲. افزودن فراداده به عناصر DOM

از نمادها می‌توان برای پیوست کردن فراداده به عناصر DOM بدون تداخل با ویژگی‌ها یا خصوصیات موجود آنها استفاده کرد.

const element = document.createElement("div");

const dataKey = Symbol("elementData");
element[dataKey] = {
  type: "widget",
  config: {},
  timestamp: Date.now()
};

// دسترسی به فراداده
console.log(element[dataKey].type); // خروجی: widget

این رویکرد فراداده را از ویژگی‌های استاندارد عنصر جدا نگه می‌دارد، قابلیت نگهداری را بهبود می‌بخشد و از تداخلات احتمالی با CSS یا سایر کدهای جاوا اسکریپت جلوگیری می‌کند.

۳. پیاده‌سازی ویژگی‌های خصوصی

اگرچه جاوا اسکریپت ویژگی‌های خصوصی واقعی ندارد، می‌توان از نمادها برای شبیه‌سازی حریم خصوصی استفاده کرد. با استفاده از یک نماد به عنوان کلید ویژگی، می‌توانید دسترسی کد خارجی به آن ویژگی را دشوار (اما نه غیرممکن) کنید.

class MyClass {
  #privateSymbol = Symbol("privateData"); // توجه: این سینتکس '#' یک فیلد خصوصی *واقعی* است که در ES2020 معرفی شده و با این مثال متفاوت است

  constructor(data) {
    this[this.#privateSymbol] = data;
  }

  getData() {
    return this[this.#privateSymbol];
  }
}

const myInstance = new MyClass("Sensitive Information");
console.log(myInstance.getData()); // خروجی: Sensitive Information

// دسترسی به ویژگی "خصوصی" (دشوار، اما ممکن)
const symbolKeys = Object.getOwnPropertySymbols(myInstance);
console.log(myInstance[symbolKeys[0]]); // خروجی: Sensitive Information

اگرچه Object.getOwnPropertySymbols() هنوز می‌تواند نماد را آشکار کند، اما احتمال دسترسی یا تغییر تصادفی ویژگی "خصوصی" توسط کد خارجی را کاهش می‌دهد. توجه: فیلدهای خصوصی واقعی (با استفاده از پیشوند #) اکنون در جاوا اسکریپت مدرن در دسترس هستند و تضمین‌های حریم خصوصی قوی‌تری ارائه می‌دهند.

بهترین شیوه‌ها برای استفاده از نمادها

در اینجا چند بهترین شیوه برای به خاطر سپردن هنگام کار با نمادها آورده شده است:

نتیجه‌گیری

نمادهای جاوا اسکریپت مکانیزم قدرتمندی برای ایجاد کلیدهای ویژگی منحصر به فرد، پیوست کردن فراداده به اشیاء و سفارشی‌سازی رفتار اشیاء ارائه می‌دهند. با درک نحوه کار نمادها و پیروی از بهترین شیوه‌ها، می‌توانید کدهای جاوا اسکریپت قوی‌تر، قابل نگهداری‌تر و بدون تداخل بنویسید. چه در حال ساخت سیستم‌های پلاگین، افزودن فراداده به عناصر DOM یا شبیه‌سازی ویژگی‌های خصوصی باشید، نمادها ابزار ارزشمندی برای بهبود گردش کار توسعه جاوا اسکریپت شما فراهم می‌کنند.